/*
    Copyright © 2010  Ahti Legonkov

    This file is part of j+-.

    j+- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    j+- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package cx.ath.skyflyer.javapm;

import cx.ath.skyflyer.javapm.util.Message;
import cx.ath.skyflyer.javapm.util.MessageCollection;
import cx.ath.skyflyer.javapm.visitor.CheckAssignmentVisitor;
import cx.ath.skyflyer.javapm.visitor.DestructorTransformVisitor;
import cx.ath.skyflyer.javapm.visitor.PrivateSymbolStripVisitor;
import cx.ath.skyflyer.javapm.visitor.SymbolTableCreationVisitor;
import cx.ath.skyflyer.javapm.visitor.SymbolTableDumpVisitor;
import cx.ath.skyflyer.javapm.visitor.SyntaxCheckVisitor;
import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.PackageDeclaration;
import japa.parser.ast.expr.NameExpr;
import japa.parser.ast.expr.QualifiedNameExpr;
import japa.parser.ast.visitor.DumpVisitor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Hashtable;
import java.util.List;
import joptsimple.OptionException;

/**
 * @author Ahti Legonkov
 */
public class JavaPm {

    private static boolean showFilenameInMessages = true;

    /**
     * Print error and warning messages to stderr.
     * @param errorMessages
     * @param file
     */
    private static void printMessages(MessageCollection errorMessages, String file) {
        for (Message msg : errorMessages.getMessages()) {
            if (showFilenameInMessages)
                System.err.print(file);
            System.err.println(msg.toString());
        }
    }

    /**
     * Returns a path that the source tree is relative to.
     */
    private static String inputDir(CommandLine cmdline) {
        if (cmdline.hasInDir()) {
            String inputDir = new File(cmdline.inputDir()).getAbsolutePath();
            if (!inputDir.endsWith(File.separator))
                inputDir = inputDir + File.separator;
            return inputDir;
        }
        
        return System.getProperty("user.dir") + File.separator;
    }

    /**
     * Returns a path that the output is written to. This path does not include
     * the package path. For example if the class is in com.something.MyClass,
     * then full output path would include path that this method returns plus
     * "com/something/MyClass.java".
     */
    private static String outputDir(CommandLine cmdline) {
        if (cmdline.hasOutDir()) {
            String outputDir = cmdline.outputDir();
            if (!outputDir.endsWith(File.separator))
                outputDir = outputDir + File.separator;
            return outputDir;
        }

        return System.getProperty("user.dir") + File.separator;
    }

	public static void main(String[] args) {
        try {
            CommandLine cmdline = new CommandLine(args);
            if (!cmdline.hasNologo())
                notice();

            if (args.length == 0) {
                usage();
                return;
            }

            if (cmdline.hasHelp() || !cmdline.hasInFile()) {
                usage();
                return;
            }

            if (cmdline.hasDiag()) {
                if (cmdline.diag().equals("nofilename"))
                    showFilenameInMessages = false;
            }

            if (!cmdline.hasInFile()) {
                System.err.println("--infile needed!");
                System.exit(1);
            }
            
            final String inputFilePath = inputDir(cmdline)/* + cmdline.inputFileName()*/;
            final InputStream in = new java.io.FileInputStream(inputFilePath+cmdline.inputFileName());

            String outputFilePath = null;
            if (cmdline.hasOutFile()) {
                // Dash means standard output. Other file nmaes are not allowed.
                outputFilePath = "-";
            }
            else {
                // if input is not specified, then output file name must be
                // specified - can't infer output file name from 'null'.
                if (inputFilePath == null) {
                    System.err.println("Please specify output file name!");
                    System.exit(1);
                }
                String infile = cmdline.inputFileName();
                String outputFileName = infile.substring(0, infile.length()-4)+".java";
                outputFilePath = outputDir(cmdline) + outputFileName;
            }

            assert(outputFilePath != null);
            
            CompilationUnit cu = JavaParser.parse(in);

            final SymbolTableCreationVisitor symbolTableCreator = new SymbolTableCreationVisitor(inputDir(cmdline), cmdline.inputFileName());
            symbolTableCreator.visit(cu, null);

            if (cmdline.hasDebug()) {
                SymbolTableDumpVisitor stdv = new SymbolTableDumpVisitor();
                stdv.visit(cu, null);
                String symbolTable = stdv.getSource();
                System.out.println(symbolTable);
            }

            final SyntaxCheckVisitor syntaxCheck = new SyntaxCheckVisitor();
            syntaxCheck.visit(cu, args);

            printMessages(syntaxCheck.getMessages(), inputFilePath);
            if (syntaxCheck.getMessages().hasErrors())
                System.exit(1);
            
            if (cmdline.isParseOnly())
                return;

            //parseImportedFiles(inputFileName, cu.getImports(), cu.getPackage());

            // make sure that types in variable declarations have readonly
            // modifier set if the declaration has it.
            //FixModifiersVisitor fmv = new FixModifiersVisitor();
            //fmv.visit(cu, null);

            final CheckAssignmentVisitor checkAssignments = new CheckAssignmentVisitor(inputDir(cmdline), cmdline.inputFileName());
            checkAssignments.visit(cu, null);

            printMessages(checkAssignments.getMessages(), inputFilePath);

            if (checkAssignments.getMessages().hasErrors())
                System.exit(1);

            final DestructorTransformVisitor destructorTransformer = new DestructorTransformVisitor();
            cu = (CompilationUnit)destructorTransformer.visit(cu, null);

            printMessages(destructorTransformer.getMessages(), inputFilePath);
            if (destructorTransformer.getMessages().hasErrors())
                System.exit(1);

            //
            // Output the transformed program.
            //

            final DumpVisitor dumper = new DumpVisitor(DumpVisitor.Mode.Java);
            dumper.visit(cu, null);

            if (!outputFilePath.equals("-")) {
                if (cmdline.hasDebug()) {
                    System.out.println("Writing: " + outputFilePath);
                }
                File dir = new File(outputFilePath).getParentFile();
                if (!dir.exists()) {
                    final boolean res = dir != null && dir.mkdir();
                    if (!res) {
                        System.err.println("Failed to create dir '"+dir.getPath()+"'.");
                        System.exit(1);
                    }
                }
            }

            final OutputStream outImpl = outputFilePath.equals("-")
                    ? System.out
                    : new FileOutputStream(outputFilePath);

            final OutputStreamWriter sw = new OutputStreamWriter(outImpl);
            sw.write(dumper.getSource());
            sw.flush();
            sw.close();
        }
        catch (IOException e) {
            System.err.println(e);
        }
        catch(OptionException e) {
            System.err.println(e.getMessage());
            usage();
        }
        catch (ParseException e) {
            System.err.println("jpm: Encountered errors during parse.");
            System.err.println(e.getMessage());
        }
    }

    /**
     * Outputs a copyright notice to standard error stream.
     */
    private static void notice() {
        System.err.println("j+- Copyright (C) 2010  Ahti Legonkov");
        System.err.println();
        System.err.println("This program comes with ABSOLUTELY NO WARRANTY. ");
        System.err.println("This is free software, and you are welcome to redistribute it");
        System.err.println("under certain conditions.");
        System.err.println();
        System.err.println("Type 'jpm --license' for details.");
        System.err.println();
    }

    /**
     * Outputs a short help on usage of this program.
     */
    private static void usage() {
        System.err.println("Usage:");
        System.err.println("\t\tjpm -input=inputfile [-output=outfile]");
        System.err.println();
        System.err.println("If inputfile is -, then standard input is used and output file must be explicitly");
        System.err.println("specified.");
        System.err.println();
    }
}
